package rolesystem.agentlogic.managers;

import java.util.ArrayList;

import rolesystem.agentlogic.parameters.ConfigurationParameters;
import rolesystem.agentlogic.utilityclasses.MoveData;
import rolesystem.core.RoleException;
import rolesystem.core.RoleRegistration;
import rolesystem.middle.managers.BooleanManager;
import rolesystem.middle.managers.CounterManager;
import rolesystem.middle.managers.DataOutputManager;
import rolesystem.middle.managers.EnvironmentManager;
import rolesystem.middle.managers.KnownEnvironmentManager;
import rolesystem.middle.utilityclasses.KnownEnvironment;
import rolesystem.middle.utilityclasses.PositionSq;
import rolesystem.middle.utilityclasses.Utils;

public class MovementManager 
{	
	/**
	 * If true the agent is the one that become the master after the mode change.
	 */
	public boolean NEW_MASTER_TO_INIT= false;
	
	/**
	 * If true the agent is the one that become a slave after the mode change.
	 */
	public boolean SLAVE_INIT= false;
	
	/**
	 * The identifier of the agent
	 */
	private int id;
	
	/**
	 * Actual position of the agent
	 */
	private PositionSq position;
	
	/**
	 * The position in witch the agent will start
	 */
	private PositionSq start_position;
	
	/**
	 * A reference to ContactManager, that manages the contact (with other agents) phase 
	 */
	private ContactManager contactManager;
	
	
	public MovementManager(int id, int start_x, int start_y, ContactManager contactManager)
	{
		this.id=id;
		this.start_position=new PositionSq(start_x,start_y);
		this.position=new PositionSq(start_x,start_y);
		this.contactManager=contactManager;
	}
	
	/**
	 * Put the agent to sleep for a number of milliseconds. 
	 * 
	 * @param pAUSE Number of ms that the agent will sleep 
	 * 
	 */	
	private void sleepAgent(long pAUSE) 
	{	
		try
		{
			Thread.sleep(pAUSE);
		} catch(InterruptedException ie) {}
	}
	
	public void reset()
	{
		this.position=new PositionSq(start_position);
		
		KnownEnvironmentManager.setKnownEnvironments(id, new KnownEnvironment());
		EnvironmentManager.updateAgentPosition(id,position);
	}
	
	public void reset(PositionSq new_start)
	{
		this.start_position=new PositionSq(new_start);
		this.position=new PositionSq(start_position);
		
		SLAVE_INIT=false;
		NEW_MASTER_TO_INIT=false;
		
		KnownEnvironmentManager.setKnownEnvironments(id, new KnownEnvironment());
		EnvironmentManager.updateAgentPosition(id,position);
	}
	
	public void setPosition(PositionSq position)
	{
		this.position=position;
		DataOutputManager.dataSim.getAgentData(id).incnumberofMovements();
		drawVisualization();
	}
	
	public PositionSq getPosition()
	{
		return new PositionSq(this.position);
	}
	
	
	private synchronized boolean checkAndSetMasterBlock(boolean thresh_check) throws RoleException
	{
		if(thresh_check)
			return BooleanManager.getAndSetIfFalse(ConfigurationParameters.MASTER_BLOCK_BOOL);
		else
			return BooleanManager.get(ConfigurationParameters.MASTER_BLOCK_BOOL);
	}
	
	private synchronized boolean checkAndSetMasterBlockInv(boolean thresh_check) throws RoleException
	{
		if(thresh_check)
			return BooleanManager.getAndSetIfFalse(ConfigurationParameters.MASTER_BLOCK_INV_BOOL);
		else
			return BooleanManager.get(ConfigurationParameters.MASTER_BLOCK_INV_BOOL);
	}
	
	private void checkThresholdRandomToMS(RoleRegistration registration) throws RoleException
	{
		if(ConfigurationParameters.KNOWN_THRESHOLD)
		{	
			long square_known=DataOutputManager.dataSim.getAgentData(id).getKnownSquare() - DataOutputManager.dataSim.getAgentData(id).getExternalKnownSquare(); // FIXME o tutte le caselle conosciute?
			long squareKnownPheromone=DataOutputManager.dataSim.getAgentData(id).getAlreadyKnownPheromone();
			
			boolean thresh_check= (squareKnownPheromone/square_known)>ConfigurationParameters.KNOWN_THRESHOLD_LIMIT;
			if(thresh_check && checkAndSetMasterBlock(thresh_check)==false)	
				contactManager.randomContact(registration);	
		}
		
		/*if(ConfigurationParameters.TIME_THRESHOLD)
		{	
			int square_known=knownEnvironment.getKnownNumber();
			long elapsed_time=DataOutputManager.timestampLong() - DataOutputManager.getSimulationStart();
			
			boolean thresh_check= square_known!=0 && ((elapsed_time/square_known)<ConfigurationParameters.TIME_THRESHOLD_LIMIT);
			if(thresh_check && checkAndSetMasterBlock(thresh_check)==false)
				contactManager.randomContact(registration);
		}*/
	}
	
	private void checkThresholdMSToRandom(RoleRegistration registration) throws RoleException
	{
		if(ConfigurationParameters.OBSTACLES_THRESHOLD_INV)
		{	
			long square_blocked=DataOutputManager.dataSim.getAgentData(id).getObstaclesKnown();//agent_data.getObstaclesKnown();
			long square_known=DataOutputManager.dataSim.getAgentData(id).getKnownSquare();
			
			boolean thresh_check= square_blocked!=0 && ((square_known/square_blocked)>ConfigurationParameters.OBSTACLES_THRESHOLD_LIMIT_INV);
			if(thresh_check && checkAndSetMasterBlockInv(thresh_check)==false)	
				contactManager.masterAndSlaveContact(registration);
		}
		
		if(ConfigurationParameters.TIME_THRESHOLD_INV)
		{	
			long square_known=DataOutputManager.dataSim.getAgentData(id).getKnownSquare();
			long elapsed_time=DataOutputManager.timestampLong() - DataOutputManager.getSimulationStart();
			
			boolean thresh_check= square_known!=0 && ((elapsed_time/square_known)>ConfigurationParameters.TIME_THRESHOLD_LIMIT_INV);
			if(thresh_check && checkAndSetMasterBlockInv(thresh_check)==false)
				contactManager.masterAndSlaveContact(registration);
		}
	}
	
	private MoveData searchUntilMove(boolean pheromone_active) throws RoleException
	{
		MoveData moveData=new MoveData();
		
		do
		{
			moveData.incRange();
			ArrayList<PositionSq> possibleRoute=KnownEnvironmentManager.getKnownEnvironments(id).searchNeighborhood(position,moveData.getRange());
			moveData.setRouteSize(possibleRoute.size());
			
			if(moveData.getRouteSize()!=0)
			{	
				boolean blocked=false;
				
				PositionSq new_position=possibleRoute.get((int)((moveData.getRouteSize()-1)*Math.random()));
				
				if(!EnvironmentManager.getObstacle(new_position)) // Mi muovo in posizione nuova  
				{
					if(moveData.getRange()==1)
						setPosition(new_position);  
					else
						reachObject(new_position); 
				}
				else
				{
					blocked=true;
					if(moveData.getRange()>1)
					{
						PositionSq dir=Utils.calcDirection(position, new_position);		
						PositionSq candidate_pos=PositionSq.diff(new_position,dir);
						
						if(!EnvironmentManager.getObstacle(candidate_pos)) 
							reachObject(candidate_pos);
						else
						{
							PositionSq extra_r=EnvironmentManager.getAlternativeRoute(new_position,PositionSq.neg(dir));
							if(!extra_r.equalTo(new_position))
								reachObject(extra_r);	
						}	
					}	
				}	
				
				if(pheromone_active)
				{	
					if(EnvironmentManager.getKnownPheromone(new_position)==false)
					{
						sleepAgent(ConfigurationParameters.EXPLORATION_PAUSE);
						EnvironmentManager.setKnownPheromone(new_position,true);
					}
					else
						DataOutputManager.dataSim.getAgentData(id).incAlreadyKnownPheromone();
				}
				
				KnownEnvironmentManager.getKnownEnvironments(id).addKnownPosition(new_position,blocked);
				
				DataOutputManager.dataSim.getAgentData(id).incKnownSquare();
			}
			
		}while(moveData.getRange()<10 && moveData.getRouteSize()==0);	
		
		return moveData;
	}
	
	private void saveMasterSlaveData(int myKnownSquares) 
	{	
		int allKnownSquares=KnownEnvironmentManager.getKnownEnvironments(id).getKnownNumber();
		
		DataOutputManager.dataSim.getAgentData(id).setKnownSquare(allKnownSquares);
		DataOutputManager.dataSim.getAgentData(id).setExternalKnownSquare(DataOutputManager.dataSim.getAgentData(id).getExternalKnownSquare()+(allKnownSquares-myKnownSquares));
		DataOutputManager.dataSim.getAgentData(id).setObstaclesKnown(KnownEnvironmentManager.getKnownEnvironments(id).getBlockedNumber());
		if((allKnownSquares-myKnownSquares)>0)
			DataOutputManager.dataSim.getAgentData(id).incGoodSyncs();
		
	}
	
	private void saveRandomData() 
	{	
		DataOutputManager.dataSim.getAgentData(id).setKnownSquare(KnownEnvironmentManager.getKnownEnvironments(id).getKnownNumber());
		DataOutputManager.dataSim.getAgentData(id).setObstaclesKnown(KnownEnvironmentManager.getKnownEnvironments(id).getBlockedNumber());
	}
	
	
	public void moveSlave(RoleRegistration registration) throws RoleException
	{	
		DataOutputManager.dataSim.getAgentData(id).setMode(2);
		
		while(BooleanManager.get(ConfigurationParameters.END_SEARCH_MOVEMENT_BOOL)==false  && BooleanManager.get(ConfigurationParameters.EXIT_AGENT_BASE_BOOL + String.valueOf(id))==false)
		{
			for(int movement_counter=0;movement_counter<5;movement_counter++) // some check are done every n movement acts
			{
				sleepAgent(ConfigurationParameters.PAUSE);
				
				int myKnownSquares=KnownEnvironmentManager.getKnownEnvironments(id).getKnownNumber();
				boolean modeChange=contactManager.receiveModeChangeMessage(registration);	
				saveMasterSlaveData(myKnownSquares);
				
				if(modeChange==true)
					return;
				
				MoveData moveData=searchUntilMove(false);
				
				if(moveData.getRange()==10 && moveData.getRouteSize()==0)
				{	
					BooleanManager.set(ConfigurationParameters.END_CHECK_BASE_BOOL + String.valueOf(id),true);
					break;
				}
				drawVisualization();
				
			}
			
			if(ConfigurationParameters.searchType!=2)
				checkThresholdMSToRandom(registration);
			
			contactManager.sendExplorationData(registration);
			
		};
		
		return;
	}
	
	public void moveMaster(RoleRegistration registration) throws RoleException
	{
		BooleanManager.set(ConfigurationParameters.END_CHECK_BASE_BOOL + String.valueOf(id),true);

		DataOutputManager.dataSim.getAgentData(id).setMode(3); // 1 random // 2 slave // 3 master
		
		while(BooleanManager.get(ConfigurationParameters.END_SEARCH_MOVEMENT_BOOL)==false  && BooleanManager.get(ConfigurationParameters.EXIT_AGENT_BASE_BOOL + String.valueOf(id))==false)
		{								
			sleepAgent(ConfigurationParameters.PAUSE-100);
			
			boolean modeChange=false;
			for(int i=0;(i<CounterManager.get(ConfigurationParameters.ID_COUNTER)) && modeChange==false;i++)
			{	
				int myKnownSquares=KnownEnvironmentManager.getKnownEnvironments(id).getKnownNumber();
				modeChange=contactManager.receiveModeChangeMessage(registration);
				saveMasterSlaveData(myKnownSquares);	
			}
			
			contactManager.refreshExplorers(registration);
			
			if(modeChange==true)
			{	
				BooleanManager.set(ConfigurationParameters.END_CHECK_BASE_BOOL + String.valueOf(id),false);
				break;
			}	
		};
	}
	
	public void moveRandom(RoleRegistration registration) throws RoleException
	{
		DataOutputManager.dataSim.getAgentData(id).setMode(1);
		
		while(BooleanManager.get(ConfigurationParameters.END_SEARCH_MOVEMENT_BOOL)==false  && BooleanManager.get(ConfigurationParameters.EXIT_AGENT_BASE_BOOL + String.valueOf(id))==false)
		{
			for(int movement_counter=0;movement_counter<10;movement_counter++) // some check are done every 10 movement acts
			{
				sleepAgent(ConfigurationParameters.PAUSE);
				
				int modeChange=contactManager.receiveModeChangeMessageRandom(registration);
				
				saveRandomData();
				
				if(modeChange==1)
				{	
					NEW_MASTER_TO_INIT=true;
					return;
				}
				
				if(modeChange==2)
				{	
					SLAVE_INIT=true;
					return;
				}
				
				MoveData moveData=searchUntilMove(ConfigurationParameters.PHEROMONE_MODE);
				
				if(moveData.getRange()==10 && moveData.getRouteSize()==0)
				{	
					BooleanManager.set(ConfigurationParameters.END_CHECK_BASE_BOOL + String.valueOf(id),true);
					break;
				}
				drawVisualization();
				
			}
			
			checkThresholdRandomToMS(registration);
			
		};
		
		return; 
	}
	
	private void reachObjectStep(PositionSq goal)
	{		
		while(true)
		{
			sleepAgent(ConfigurationParameters.PAUSE);
			
			PositionSq direction= Utils.calcDirection(position, goal);
			
			if(direction.isZero())
				break;
			
			if(EnvironmentManager.getObstacle(PositionSq.add(position, direction))==true)
				position.set(EnvironmentManager.getAlternativeRoute(position,direction));			
			else
				position.add(direction);
			
			DataOutputManager.dataSim.getAgentData(id).incnumberofMovements();
			
			drawVisualization();
		}
	}
	
	
	public void reachObject(PositionSq goal)
	{
		reachObjectStep(goal);	
		//DataOutputManager.printString(id +" Objective Reached");
	}
	
	
	public void drawVisualization()
	{	
		EnvironmentManager.updateAgentPosition(id,position);
		EnvironmentManager.updateVisualization();	
	}
	
}
